// patch 算法
import * as nodeOps from "./node-ops";

function isUndef(s) {
    return s == null
}

function isDef(s) {
    return s != null;
}

function sameVnode(vnode1, vnode2) {
    return vnode1.tag === vnode2.tag;
}

// 删除节点
function removeNode(el) {
    // 获取 el 的父节点
    const parent = nodeOps.parentNode(el);
    if (parent) {
        nodeOps.removeChild(parent, el);
    }
}

// 通过 createElm 当前 VNode 节点渲染到对应的 DOM 树上
function createElm(vnode, parentElm, refElm) {
    const children = vnode.children;
    const tag = vnode.tag;

    if (isDef(tag)) { // 非文本节点
        // 1、先创建当前节点
        vnode.elm = nodeOps.createElement(tag);
        // 2、创建子节点
        createChildren(vnode, children);
        // 3、把当前节点插入到 dom 上
        insert(parentElm, vnode.elm, refElm);
    } else {
        // 文本节点
        vnode.elm = nodeOps.createTextNode(vnode.text);
        insert(parentElm, vnode.elm, refElm);
    }
}

// createChildren 递归创建当前 VNode 节点的子节点
function createChildren(vnode, children) {
    if (Array.isArray(children)) {
        for (let i = 0; i < children.length; ++i) {
            createElm(children[i], vnode.elm, null)
        }
    }
}

// insert 函数，可以把一个 elm dom对象，插入到 parent dom 上
function insert(parent, elm, ref) {
    if (parent) {
        if (ref) {
            nodeOps.insertBefore(parent, elm, ref);
        } else {
            nodeOps.appendChild(parent, elm);
        }
    }
}
// 循环创建 VNode
function addVnodes(parentElm, refElm, vnodes, startIdx, endIdx) {
    for (; startIdx <= endIdx; ++startIdx) {
        createElm(vnodes[startIdx], parentElm, refElm)
    }
}

// 删除多少个 dom
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
    for (; startIdx <= endIdx; ++startIdx) {
        const ch = vnodes[startIdx];
        if (isDef(ch)) {
            removeNode(ch.elm);
        }
    }
}


function updateChildren(parentElm, oldCh, newCh, removeOnly) {
    let oldStartIdx = 0;
    let oldEndIdx = oldCh.length - 1;
    let oldStartVnode = oldCh[0];
    let oldEndVnode = oldCh[oldEndIdx];
    let newStartIdx = 0;
    let newEndIdx = newCh.length - 1;
    let newStartVnode = newCh[0];
    let newEndVnode = newCh[newEndIdx];
    let refElm;
    const canMove = !removeOnly;


    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isUndef(oldStartVnode)) {
            oldStartVnode = oldCh[++oldStartIdx];
        } else if (isUndef(oldStartVnode)) {
            oldEndVnode = oldCh[--oldEndIdx];
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
            // 1、oldStartVnode 和 newStartVnode 相同
            // 递归 patch 2个节点，然后挪动2个指针位置
            patchVnode(oldStartVnode, newStartVnode);
            oldStartVnode = oldCh[++oldStartIdx];
            newStartVnode = newCh[++newStartIdx];
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
            // 2、oldEndVnode 和 newEndVnode 相同
            // 同上第1点
            patchVnode(oldEndVnode, newEndVnode);
            oldEndVnode = oldCh[--oldEndIdx];
            newEndVnode = newCh[--newEndIdx];
        } else if (sameVnode(oldStartVnode, newEndVnode)) {
            // 3、oldStartVnode 和 newEndVnode 相同
            // 递归 patch 两个节点，同时把 oldStartDom 挪动 oldEndDom 之后
            patchVnode(oldStartVnode, newEndVnode);
            canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
            oldStartVnode = oldCh[++oldStartIdx];
            newEndVnode = newCh[--newEndIdx];
        } else if (sameVnode(oldEndVnode, newStartVnode)) {
            // 4、oldEndVnode 和 newStartVnode 相同
            // 递归 patch 两个节点，同时把 oldEndDom 挪到 oldStartDom 之前
            patchVnode(oldEndVnode, newStartVnode);
            canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
            oldEndVnode = oldCh[++oldEndIdx];
            newStartVnode = newCh[--newStartIdx];
        } else {
            // 直接创建新的 dom，插入到原来的父节点底下
            createElm(newStartVnode, parentElm, oldStartVnode.elm);
            newStartVnode = newCh[++newStartIdx];
        }
    }
    if (oldStartIdx > oldEndIdx) {
        refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
        addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx);
    } else if (newStartIdx > newEndIdx) {
        removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
    }
}

function patchVnode(oldVnode, vnode, removeOnly) {
    if (oldVnode === vnode) return;

    const elm = vnode.elm = oldVnode.elm;
    const oldCh = oldVnode.children;
    const ch = vnode.children;

    if (isUndef(vnode.text)) {
        if (isDef(oldCh) && isDef(ch)) {
            // 如果旧的children 跟 新的children不一样，则递归
            if (oldCh !== ch) updateChildren(elm, oldVnode, ch, removeOnly);
        } else if (isDef(ch)) {
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "");
            addVnodes(elm, null, ch, 0, ch.length - 1)
        } else if (isDef(oldCh)) {
            // 删除旧节点
            removeVnodes(elm, oldCh, 0, oldCh.length - 1);
        } else if (isDef(oldVnode.text)) {
            nodeOps.setTextContent(elm, '');
        }
    } else if (oldVnode.text !== vnode.text) {
        nodeOps.setTextContent(elm, vnode.text)
    }
}

// 使用 updateChildren 继续递归对比他们子节点
export default function patch(oldVNode, newVNode) {
    let isInitialPatch = false;

    if (sameVnode(oldVNode, newVNode)) {
        // 如果两个 VNode 节点根一致
        patchVnode(oldVNode, newVNode);
    } else {
        // 既然来到这里 就说明两个 VNode 的dom的根节点不一样
        // 只是拿到原来的 dom 的容器 parentElm，把当前 VNode 的所有 dom 生成进去
        // 然后把以前的 old VNode 全部移除掉

        // 1、找到 oldVNode 对应的 dom 节点及其父节点
        const oldElm = oldVNode.elm;
        const parentElm = nodeOps.parentNode(oldElm);

        // 2、把当前的 vnode 渲染在原来的父亲节点下
        createElm(newVNode, parentElm, nodeOps.nextSibling(oldElm));

        if (parentElm !== null) {
            // 3.把旧的 DOM 节点从原来的 dom 树上移除
            removeVnodes(parentElm, [oldVNode], 0, 0);
        }
    }
    return newVNode.elm;
}
